iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0

上一篇我們講解怎麼產生目標 parser 的 parse 方法,這篇來講解 generator 的內部結構,這會用到上篇提到的 getParseFuncSpec 。我們的 generator 會先繼承一個基底類別,叫做 ParserGenerator ,裡面是包含一些 generator 的基礎方法和流程。

const val METHOD_GET_ATTR_OR_NULL = "getAttributeOrNull"
const val METHOD_GET_ELEMENT_BY_TAG = "getElementByTag"

class KotlinParserGenerator(
    private val element: Element,
    private val isRoot: Boolean,
    logger: Logger
) : ParserGenerator(logger) {

    private val outputClass = ClassName(element.getPackage(), element.simpleName.toString())
    private val exceptionClass = ClassName("java.lang", "IllegalStateException")
    private val docBuilderFactoryClass = ClassName("javax.xml.parsers", "DocumentBuilderFactory")
    private val elementClassName = ClassName("org.w3c.dom", "Element")
    private val listClassName = ClassName("java.util", "ArrayList")
    private val getAttributeOrNullMemberName = MemberName(extensionFullPath, METHOD_GET_ATTR_OR_NULL)
    private val getElementByTagMemberName = MemberName(extensionFullPath, METHOD_GET_ELEMENT_BY_TAG)

    override fun generate(): FileSpec {
        val generatedClassName = "${element.simpleName}$PARSER_SUFFIX"
        return FileSpec.builder(GENERATOR_PACKAGE, generatedClassName)
            .addType(getObjectTypeSpec(generatedClassName))
            .build()
    }

    private fun getObjectTypeSpec(className: String): TypeSpec {
        val builder = TypeSpec.objectBuilder(className)
        val outputClassName = element.simpleName.toString()

        if (isRoot) {
            builder.addFunction(getParseFuncSpec())
        }
        return builder
            .addFunction(getClassFunSpec(element, outputClassName, builder))
            .build()
    }

		private fun getParseFuncSpec(): FunSpec {
				// 略
		}

		private fun getClassFunSpec(
        rootElement: Element,
        outputClassName: String,
        objectBuilder: TypeSpec.Builder
    ): FunSpec {
				// 略
		}
}

在 annotation processor 偵測到 annotation 元素時,就會呼叫 parser generator 幫它產生對應的程式碼,只要放入相對應的 Element 就可以,而在建構仔我們看到的 isRoot 指的是這個 element 是不是 channel tag 。之後,generator 的 generate 方法就會被呼叫,把用 KotlinPoet 產生的程式碼寫入檔案中。上方程式碼裡的 getClassFunSpec 就是用來針對每個被標註 @RssTag 的類別產生對應的 parser 程式碼,也是本篇的重點。要怎麼針對它產生對應的 parser 程式碼?我們先想想產生出來的程式碼應該要長怎麼樣。假設我們有一個 data class RssItem

@RssTag(name = "item")
data class RssItem(
    val title: String?,
    val author: String?,
    val guid: TestGuid?
): Serializable

@RssTag(name = "guid")
data class TestGuid(
    @RssAttribute
    val isPermaLink: Boolean?
): Serializable

那它產生出來的程式碼,預計要長成這樣:

object RssItemParser {
	fun Element.getItem(): RssItem {
			// #1 Value Statement
			val titleTitle: String? = readString("title")
	    val titleItunesTitle: String? = readString("itunes:title")
	    val titleGoogleplayTitle: String? = readString("googleplay:title")
	    val authorAuthor: String? = readString("author")
	    val authorItunesAuthor: String? = readString("itunes:author")
	    val authorGoogleplayAuthor: String? = readString("googleplay:author")
	    val guidGuid: TestGuid? = getElementByTag("guid")?.getGuid()
	    val guidItunesGuid: TestGuid? = getElementByTag("itunes:guid")?.getGuid()
	    val guidGoogleplayGuid: TestGuid? = getElementByTag("googleplay:guid")?.getGuid()
	
			// #2 Use class constructor
      return RssItem(
	  		title = titleTitle ?: titleItunesTitle ?: titleGoogleplayTitle,
	  		author = authorAuthor ?: authorItunesAuthor ?: authorGoogleplayAuthor,
	  		guid = guidGuid ?: guidItunesGuid ?: guidGoogleplayGuid
	  	)
	}
}

為了要產生上面的程式碼,我們可以把步驟拆成四個:

Generator process.drawio.png

  • Annotation Pre-processing
    • 預先處理一些 annotation ,將它們放在一個有名稱和 annotation 的對照 map 裡面。
  • Convert to ParseData
    • 將上個步驟的資訊,轉成一個自定義的 data class ParseData
  • Generate Value Statement
    • ParseData 整理好的資訊來產生 value 宣告的程式碼。(上面程式碼標註 #1 的部分)
  • Generate Constructor
    • 接續上個步驟產生的 value 宣告,用它們來產生 constructor 的程式碼。(上面程式碼標註 #2 的部分)

下篇文章我會各別把這四個步驟用程式碼來講解他們的實作。


上一篇
Parser Generator (一)
下一篇
Parser Generator (三)
系列文
如何使用 Kotlin Annotation Processor 做出自己的 Custom Data Parser Library30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言